import x2js from 'x2js'

const config = {
  hostUrl: 'wss://ise-api.xfyun.cn/v2/open-ise',
  host: 'localhost',
  appid: '613b2e2a',
  apiSecret: 'MmM2YjU2MjdiY2E1Y2FjNTA2YzRmZDkz',
  apiKey: '52689767ebeba82989ddb8f3f16e4027',
  uri: '/v2/open-ise'
}

const audio = new Audio();

const audioKey = 'audio-expression'
let mediaRecorder;
let audioChunks = [];
let audioBlob = null;

let isRecording = false
// const score = ref('')
let text = '';
let category = '';
let callback = null;
let callback1 = null;

let x2 = new x2js();

let mediaStream
let audioContext
let processor
let websocket

function checkMicrophonePermission() {
  // return new Promise((resolve, reject) => {
  //   // 查询麦克风权限
  //   navigator.permissions.query({ name: 'microphone' })
  //     .then((res) => {
  //       console.log('checkMicrophonePermission', res);
  //       if (res.state === 'granted') {
  //         // 已授权
  //         resolve(true);
  //       } else if (res.state === "prompt") {
  //         // 请求授权
  //         resolve(true);
  //       } else {
  //         // 拒绝
  //         reject(false);
  //       }

  //     })
  // });
}

// 查询是否有麦克风|话筒
async function hasAudioinput() {
  return new Promise(async (resolve, reject) => {
    if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
      console.log("不支持mediaDevices.enumerateDevices(), 未识别到多媒体设备！");
      reject(false);
    }
    navigator.mediaDevices.enumerateDevices().then(function (devices) {
      let flag = false;
      console.log('devices',devices);
      devices.forEach(function (device) {
        switch (device?.kind) {
          case 'audioinput':
            console.log('音频输入设备(麦克风|话筒)：', device);
            flag = true;
            break;
          case 'audiooutput':
            // console.log('音频输出设备(扬声器|音响)：', device);
            break;
          case 'videoinput':
            // console.log('视频输入设备(摄像头|相机)：', device);
            break;
          default:
            // console.log('当前可用的媒体设备: ', device);
            break;
        }
      });
      flag ? resolve(true) : reject(false);
    }).catch(function (err) {
      console.error(err);
      reject(false);
    });
  });
};

// errorCode  40001：权限被拒绝  40002：连接websocket失败  40003：代码执行报错，位置错误

// 获取媒体流数据
async function startRecording(content, type, cb, cb1) {
  text = content;
  category = type;
  callback = cb;
  callback1 = cb1;
  return new Promise(async (resolve, reject) => {
    try {
      const options = {
        audio: true,    // 注：这里 audio、video 默认都为false【一定要确保有麦克风或摄像头（有啥设备就开启啥设备）硬件设备的情况下才设为true 或 {...}，否则会报DOMException: Requested device not found 等错！！】
        // video: true,	// 获取视频 默认video: { facingMode: 'user' } }前置相机
        // video: {
        //     width: 1280, // 设置摄像头分辨率
        //     height: 720,
        //     facingMode: { exact: 'environment' } // 获取视频 后置相机
        // }
      }
      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        // 访问用户媒体设备的 新标准API
        navigator.mediaDevices.getUserMedia(options).then(async function (mediaStream) {
          handleMediaStream(mediaStream).then(() => {
            resolve(true);
          }).catch((errorCode) => {
            reject(errorCode)
          });
        }).catch(function (err) {
          console.error("1111 访问用户媒体设备失败：权限被拒绝", err);
          reject(40001)
        })
      } else if (navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia) {
        //访问用户媒体设备的 旧标准API 兼容方法navigator.getUserMedia(options, function (MediaStream) {handleMediaStream(MediaStream)},function (err) {  console.error("访问用户媒体设备失败：权限被拒绝 或 未识别到多媒体设备！", err);
        navigator.getUserMedia(options, async function onSuccess(mediaStream) {
          handleMediaStream(mediaStream).then(() => {
            resolve(true);
          }).catch((errorCode) => {
            reject(errorCode)
          });
        }, function onError(error) {
          console.error("222 访问用户媒体设备失败：权限被拒绝", err);
          reject(40001)
        });
      } else {
        // navigator.mediaDevices在目前以下三种情况下可以获取到
        // 1. 地址为localhost:// 访问时
        // 2. 地址为https:// 时
        // 3. 为文件访问file:///
        // if (0 > location.origin.indexOf('https://')) {
        //   console.error("提示：请尝试在本地localhost域名 或 https协议 的Web服务器环境中重新运行！");
        // }
        console.error("333 访问用户媒体设备失败：权限被拒绝");
        reject(40001)
      }
    } catch (error) {
      console.error("录音失败：", error);
      reject(40003)
    }
  });
}

async function handleMediaStream(stream) {
  return new Promise(async (resolve, reject) => {
    try {
      mediaStream = stream;

      if (callback) {
        // console.log('callback');
        initWebSocket().then(() => {
          processorAudio(stream);
          if (callback1) {
            // console.log('callback1');
            saveRecorder(stream)
          }
          resolve(true);

        }).catch(() => {
          reject(40002)
        });
      }
    } catch (error) {
      reject(40003)
    }
  });
}

async function processorAudio(mediaStream) {
  // audioContext = new AudioContext({ sampleRate: 16000 })
  audioContext = new AudioContext()
  const source = audioContext.createMediaStreamSource(mediaStream)

  processor = audioContext.createScriptProcessor(4096, 1, 1)

  source.connect(processor)
  processor.connect(audioContext.destination)

  processor.onaudioprocess = (e) => {
    // console.log('e', e);
    const data = e.inputBuffer.getChannelData(0);

    const pcmData = floatTo16BitPCM(interpolateArray(new Float32Array(data), 16000, audioContext.sampleRate))
    sendData(pcmData)
  }
}

function linearInterpolate(before, after, atPoint) {
  return before + (after - before) * atPoint;
};

function interpolateArray(data, newSampleRate, oldSampleRate) {
  var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate));
  var newData = new Array();
  var springFactor = new Number((data.length - 1) / (fitCount - 1));
  newData[0] = data[0]; // for new allocation
  for (var i = 1; i < fitCount - 1; i++) {
    var tmp = i * springFactor;
    var before = new Number(Math.floor(tmp)).toFixed();
    var after = new Number(Math.ceil(tmp)).toFixed();
    var atPoint = tmp - before;
    newData[i] = linearInterpolate(data[before], data[after], atPoint);
  }
  newData[fitCount - 1] = data[data.length - 1]; // for new allocation
  return newData;
};


const initWebSocket = async () => {
  return new Promise(async (resolve, reject) => {
    const date = new Date().toUTCString()
    const authStr = await getAuthStr(date)
    const url = `${config.hostUrl}?authorization=${authStr}&date=${encodeURIComponent(date)}&host=${config.host}`

    websocket = new WebSocket(url)

    websocket.onopen = () => {
      console.log('WebSocket connection established.')
      sendFirstFrame()
      resolve(true)
    }

    websocket.onmessage = (event) => {
      // const res = JSON.parse(event.data)
      // if (res.code !== 0) {
      //   console.error(`Error code ${res.code}, reason ${res.message}`)
      //   return
      // }

      // if (res.data && res.data.status === 2) {
      //   const data = res.data.data 
      //   const decodedData = atob(data)
      //   // // console.log(decodedData)
      //   // const result = JSON.parse(decodedData)
      //   // // // console.log('Final result:', result)
      //   // score.value = result.practice.total_score.toFixed(2)
      // }
      // 识别结束
      let resultData = event.data;
      let jsonData = JSON.parse(resultData);
      console.log('websocket.onmessage', jsonData.data);

      if (jsonData.data && jsonData.data.data) {
        // // console.log(jsonData.data.data);

        let xmlData = window.atob(jsonData.data.data);
        // console.log(xmlData);

        // 将xml解析为我们需要的json对象
        let grade = x2.xml2js(xmlData);
        console.log(grade);

        if (category == 'sentence') {
          const readSentence =
            grade?.xml_result?.read_sentence?.rec_paper?.read_chapter;

          console.log('readSentence', readSentence);
          callback && callback(
            readSentence?._is_rejected == 'false' ? {
              fluency: readSentence?._fluency_score,
              integrity: readSentence?._integrity_score,
              accuracy: readSentence?._accuracy_score,
              total: readSentence?._total_score
            } : {
              fluency: 0,
              integrity: 0,
              accuracy: 0,
              total: 0
            }
          );
        } else if (category == 'word') {
          const readWord =
            grade?.xml_result?.read_word?.rec_paper?.read_word;

          console.log('readWord', readWord);
          callback && callback(
            readWord?._is_rejected == 'false' ? {
              total: readWord?._total_score
            } : {
              total: 0
            }
          );
        }
        // // console.log('callback',callback);
      }
      if (jsonData.code === 0 && jsonData.data.status === 2) {
        websocket.close();
      }
      if (jsonData.code !== 0) {
        websocket.close();
        console.error(jsonData);
      }

    }

    websocket.onerror = (err) => {
      console.error('WebSocket error:', err)
      reject(false)
    }

    websocket.onclose = () => {
      console.log('WebSocket connection closed.')
    }
  });
}

const saveRecorder = async (stream) => {
  /* 保存录制音频逻辑  start*/
  // const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  mediaRecorder = new MediaRecorder(stream);
  mediaRecorder.ondataavailable = (event) => {
    audioChunks.push(event.data);
  };
  mediaRecorder.onstop = () => {
    console.log('mediaRecorder.mimeType',mediaRecorder.mimeType);

    audioBlob = new Blob(audioChunks, { type: 'audio/mp3' });
    callback1 && callback1(audioBlob);
    stream.getTracks().forEach((track) => track.stop());

    // if (audioChunks.length > 0) {
    //   const saveAudioBlob = async (id = "audioBlob", audioBlob) => {
    //     return new Promise((resolve, reject) => {
    //       const request = indexedDB.open("audioDB", 1);
    //       request.onerror = (event) => reject(event);
    //       request.onsuccess = (event) => {
    //         const db = event.target.result;
    //         const transaction = db.transaction("audioChunks", "readwrite");
    //         const store = transaction.objectStore("audioChunks");
    //         store.put({ id, data: audioBlob });

    //         transaction.oncomplete = () => resolve();
    //         transaction.onerror = (event) => reject(event);
    //       };

    //       request.onupgradeneeded = (event) => {
    //         const db = event.target.result;
    //         db.createObjectStore("audioChunks", { keyPath: "id" });
    //       };
    //     });
    //   };
    //   saveAudioBlob(audioKey, audioBlob)
    //     .then(() => {
    //       // console.log('--------',saveAudioBlob);
    //       callback1 && callback1(true);
    //     })
    //     .catch((err) => {
    //       console.error("保存音频时出错:", err);
    //       callback1 && callback1(false);
    //     });
    // }
    audioChunks = [];
  };
  mediaRecorder.start();
  /* 保存录制音频逻辑  end*/
}

const stopRecording = () => {
  // if (!isRecording) {
  //   return
  // }
  // isRecording = false

  if (mediaRecorder && mediaRecorder.state !== "inactive") {
    mediaRecorder.stop();
  }

  if (processor && audioContext) {
    processor.disconnect()
    audioContext.close()
  }

  if (mediaStream) {
    mediaStream.getTracks().forEach((track) => track.stop())
  }

  sendLastFrame()
}

function floatTo16BitPCM(input) {
  const output = new Int16Array(input.length)
  for (let i = 0; i < input.length; i++) {
    let s = Math.max(-1, Math.min(1, input[i]))
    output[i] = s < 0 ? s * 0x8000 : s * 0x7fff
  }
  return output
}

function arrayBufferToBase64(buffer) {
  let binary = ''
  const bytes = new Uint8Array(buffer)
  const len = bytes.byteLength
  for (let i = 0; i < len; i++) {
    binary += String.fromCharCode(bytes[i])
  }
  return btoa(binary)
}

async function getAuthStr(date) {
  const signatureOrigin = `host: ${config.host}\ndate: ${date}\nGET ${config.uri} HTTP/1.1`
  const encoder = new TextEncoder()
  const keyData = encoder.encode(config.apiSecret)
  const signatureData = encoder.encode(signatureOrigin)

  const cryptoKey = await crypto.subtle.importKey(
    'raw',
    keyData,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  )

  const signature = await crypto.subtle.sign('HMAC', cryptoKey, signatureData)
  const signatureBase64 = arrayBufferToBase64(signature)

  const authorizationOrigin = `api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signatureBase64}"`
  const authStr = btoa(authorizationOrigin)
  return authStr
}

const sendFirstFrame = () => {
  const frame = {
    common: { app_id: config.appid },
    business: {
      sub: 'ise',
      ent: 'en_vip',
      category: `read_${category}`,
      text: category == 'word' ? `[word] ${text}` : `\uFEFF${text}`,
      tte: 'utf-8',
      rstcd: 'utf8',
      ttp_skip: true,
      cmd: 'ssb',
      aue: 'raw',
      auf: 'audio/L16;rate=16000',
      aus: 1,
      group: 'pupil'
    },
    data: { status: 0 }
  }
  websocket.send(JSON.stringify(frame))
}

const sendData = (pcmData) => {
  if (websocket && websocket.readyState === WebSocket.OPEN) {
    const base64Data = arrayBufferToBase64(pcmData.buffer)

    const frame = {
      common: { app_id: config.appid },
      business: {
        aus: 2,
        cmd: 'auw',
        aue: 'raw'
      },
      data: { status: 1, data: base64Data }
    }
    websocket.send(JSON.stringify(frame))
  }
}

const sendLastFrame = () => {
  if (websocket && websocket.readyState === WebSocket.OPEN) {
    const frame = {
      common: { app_id: config.appid },
      business: {
        aus: 4,
        cmd: 'auw',
        aue: 'raw'
      },
      data: { status: 2, data: '' }
    }
    websocket.send(JSON.stringify(frame))
  }
}

function pauseRecord() {
  audio.pause();
}
//播放录音
async function PlayRecording(audioKey = 'audio-expression') {
  const openDatabase = () => {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open("audioDB", 1);

      request.onerror = (event) => {
        console.error("Database error:", event.target.error);
        reject(event.target.error);
      };

      request.onsuccess = (event) => {
        // // console.log("Database opened successfully");
        resolve(event.target.result);
      };

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains("audioChunks")) {
          db.createObjectStore("audioChunks", { keyPath: "id" });
          // // console.log("Object store created");
        }
      };
    });
  };
  // 从数据库中获取音频数据
  const getAudioBlob = async (id) => {
    const db = await openDatabase(); // 确保数据库已打开
    return new Promise((resolve, reject) => {
      const transaction = db.transaction("audioChunks", "readonly");
      const store = transaction.objectStore("audioChunks");

      const request = store.get(id);
      request.onsuccess = (event) => {
        const result = event.target.result;
        if (result) {
          // // console.log("Audio blob retrieved successfully:", result.data);
          resolve(result.data);
        } else {
          // // console.log("No data found for the given id:", id);
          reject("No data found");
        }
      };

      request.onerror = (event) => {
        console.error("Error retrieving audio blob:", event.target.error);
        reject(event.target.error);
      };
    });
  };
  // 调用示例
  getAudioBlob(audioKey)
    .then((audioBlob) => {
      const audioUrl = URL.createObjectURL(audioBlob);
      audio.src = audioUrl;
      audio.play();
    })
    .catch((error) => {
      console.error("Failed to retrieve audio blob:", error);
    });
}

export default {
  startRecording,
  stopRecording,
  PlayRecording,
  pauseRecord,
  checkMicrophonePermission,
  hasAudioinput
}

{/* <template>
  <n-card style="margin-top: 3vh">
    <n-button @click="isRecording ? stopRecording() : startRecording()">
      {{ isRecording ? '停止录音' : '录音' }}
    </n-button>
    <div>分数：{{ score }}</div>
  </n-card>
</template> */}
